/*
 *      Game.java
 *      
 * 
 */

/* Navin Shajan       1/28/2011
 * 
 * 
 * 
 * 3 Layouts:
 * CardLayout
 * BorderLayout
 * ~GridLayout
 * 
 * 5 Components:
 * JRadioButton
 * JButton
 * JPanel
 * JSlider
 * JTextPanel
 * ~~
 * xv= v * cos(a)
 * yv = v* sin(a)
 * 
 * xv=x speed
 * yv=y speed
 * 
 * x=x+xv*dt
 * y=y+yv*dt
 * 
 * yv=yv-g*dt
 */
import static java.lang.Math.*;

import java.awt.*;
import java.awt.event.*;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

//TODO add status at bottom
//TODO check drag and drop package
//FIXME warmup VM
@SuppressWarnings("serial")
public class Game extends JFrame implements MouseListener, MouseMotionListener,
		KeyListener {

	private final class Display extends JPanel {
		private Graphics2D g2;
		private int count = 0;

		Display() {
			setBackground(Color.cyan);
		}

		private void paintBackground() {
			for (int i = 0; i < Game.DOTSIZE / 2; i += 2)
				g2.fill(new Rectangle2D.Double((BackgroundDots.get(i) * 800
						- DotShift + (Integer.MAX_VALUE - 10000)) % 800,
						BackgroundDots.get(i + 1) * 500, 1, 1));
			for (int i = 0; i < 3; i++)
				g2.drawImage(
						clouds,
						((int) (seeds.get(count = count + 1) * 825 - CloudShift)
								+ Integer.MAX_VALUE - 10000) % 925 - 100,
						(int) (100 * seeds.get(count = count + 1)), 100, 50,
						this);
			for (int i = 0; i < 5; i++)
				g2.drawImage(floor, (int) (272 * i - FloorShift
						+ Integer.MAX_VALUE - 10000) % 1100 - 272, 550, 272,
						75, this);
			g2.drawImage(wall, (int) (1500 - FloorShift), 135, this);
		}

		@Override
		public void paintComponent(final Graphics g) {
			count = 0;
			g2 = (Graphics2D) g;
			super.paintComponent(g2);
			setQ();
			paintBackground();
			if (!yPath.isEmpty())
				g2.drawImage(bull, (int) round(xPath.get(timex) > 300 ? 300
						: xPath.get(timex)) + 100, 800 - (int) round(yPath
						.get(timex)), 55, 55, this);
			paintForeground();
			g2 = null;
			if (needFocus) {
				requestFocus();
				needFocus = false;
			}
		}

		private void paintForeground() {
			g2.setColor(Color.MAGENTA);
			g2.drawString("FPS: " + round(fps), 625, 25);
			g2.drawString("Score: " + score, 625, 65);
		}

		private void setQ() {
			g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
					RenderingHints.VALUE_ANTIALIAS_ON);
			g2.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION,
					RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
			g2.setRenderingHint(RenderingHints.KEY_RENDERING,
					RenderingHints.VALUE_RENDER_QUALITY);
			g2.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
					RenderingHints.VALUE_STROKE_NORMALIZE);
			g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
					RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
			g2.setFont(g2.getFont().deriveFont(18f));
		}
	}

	final class introPanel extends JPanel {
		private introPanel() {
			setLayout(new BorderLayout());
			add(new JTextArea("Some instructions: TODO"));
		}
	}// TODO

	@Deprecated
	final class ModePanel extends JPanel implements ActionListener {
		private static final long serialVersionUID = 1L;

		private ModePanel() {
			final JButton sud1 = new JButton("Back");
			sud1.addActionListener(this);
			this.add(sud1, BorderLayout.NORTH);
			this.add(new introPanel());// TODO
		}

		@Override
		public void actionPerformed(final ActionEvent evt) {
			final String command = evt.getActionCommand();
			if (command.equals("Back")) {
				mainCardLayout.first(cont);
				needFocus = true;
			}
		}
	}

	@Deprecated
	final class RadioPanel extends JPanel implements ActionListener {
		private final JRadioButton one = new JRadioButton("Instructions"),
				two = new JRadioButton("Play"), three = new JRadioButton(
						"High Score Board");
		private final ButtonGroup buttons = new ButtonGroup();

		private RadioPanel() {
			// setLayout(new GridLayout(4, 1));
			buttons.add(one);
			buttons.add(two);
			buttons.add(three);
			add(one);
			add(two);
			add(three);
			one.addActionListener(this);
			two.addActionListener(this);
			three.addActionListener(this);
		}

		@Override
		public void actionPerformed(final ActionEvent e) {
			if (one.isSelected())
				mainCardLayout.show(cont, "intro");
			else if (two.isSelected())
				mainCardLayout.show(cont, "game");
			else if (three.isSelected())
				mainCardLayout.show(cont, "hs");
		}
	}

	/**
	 * @Magic
	 * @author Navin Shajan
	 * @SafeForSpeedsNotExceeding(units = SpeedUnits.SMOOTS_PER_MICROCENTURY,
	 *                                  value = 1000)
	 */
	final class Refresh extends Thread {
		private Refresh() {
			super("Thread-Refresh");
		}

		@Override
		public void run() {
			long time = System.currentTimeMillis();
			long count = 0;
			while (true) {
				if (calc() || needFocus)
					display.repaint();// repaint if values changed/need focus
				try {
					Thread.sleep(s1.getValue() / 50);
					// Thread.sleep(s1.getValue() / 2500);
				} catch (final InterruptedException e) {
					e.printStackTrace();
				}
				count++;
				if (System.currentTimeMillis() - time > 1000) {
					fps = count
							/ ((System.currentTimeMillis() - time) / 1000.0);
					count = 0;
					time = System.currentTimeMillis();
				}
			}
		}
	}

	public static void main(final String[] args) throws IOException {
		// all OR s should shortciruit asap :
		// if([no args] or [loader])
		if (args.length < 1 || !"-noLoader".matches(args[0])) {
			final String[] flagsArr = {
					"-XX:+AggressiveOpts ",
					"-XX:CompileThreshold=10000 ",
					"-XX:+UseFastAccessorMethods ",
					"-XX:+UseStringCache ",
					"-XX:+OptimizeStringConcat ",
					"-XX:MaxInlineSize=35" 
			};
			String flags = "";
			String readableFlags = "";
			for (final String flag : flagsArr) {
				flags += flag;
				readableFlags += flag + "\n";
			}
			System.out.println("\n\n Loading game with flags:\n" + readableFlags
					+ "\n");
//			ProcessBuilder pb =
//					   new ProcessBuilder("myCommand", "myArg1", "myArg2");
//					 pb.directory(new File("myDir"));
//					 File log = new File("log");
//					 pb.redirectErrorStream(true);
//					 pb.redirectOutput(Redirect.appendTo(log));
//					 Process p = pb.start();
//					 assert pb.redirectInput() == Redirect.PIPE;
//					 assert pb.redirectOutput().file() == log;
//					 assert p.getInputStream().read() == -1;
			try {
				Runtime.getRuntime()
						.exec("java " + flags + " Game -noLoader",
								null,
								System.getProperty("os.name").equals("Windows 7") ? new File(
										"bin/") : null);
			} catch (final IOException ioe) {
				ioe.printStackTrace();
			}
			return;
		}
		System.out.println("printing to game's out stream");
		final String s = System.getProperty("os.name").equals("Windows 7") ? "C:\\Users\\Navin\\Dropbox\\workspace\\Game\\"
				: "";
		/*
		 * final Image clouds = Toolkit.getDefaultToolkit().createImage( s +
		 * "2_clouds.png"); final Image floor =
		 * Toolkit.getDefaultToolkit().createImage( s + "tile.jpg"); final Image
		 * bull = Toolkit.getDefaultToolkit().createImage( s + "bull.png");
		 */
		final Game mii = new Game(ImageIO.read(new File(s + "2_clouds.png")),
				ImageIO.read(new File(s + "tile.jpg")), ImageIO.read(new File(s
						+ "bull.png")), ImageIO.read(new File(s + "wall.png")));
		// reinit before initing :)
		mii.init();
		mii.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		mii.setLocation(20, 60);
		mii.setSize(800, 800);
		mii.setTitle("Game");
		System.runFinalization();
		System.gc();
		mii.setVisible(true);
	}

	private int score;

	private final JButton b1 = new JButton("Fire", null) {
		{
			setVerticalTextPosition(SwingConstants.CENTER);
			setHorizontalTextPosition(SwingConstants.LEADING);
			setMnemonic(KeyEvent.VK_F);
			setActionCommand("fire");
			addActionListener(new ActionListener() {
				@Override
				public void actionPerformed(final ActionEvent e) {
					if (e.getActionCommand().equals("fire"))
						fire();
				}
			});
			setToolTipText("Click this button to fire.");
		}
	};

	private double FloorShift, DotShift, CloudShift;
	private final double BIAS = random() / 100;
	private double xSpeed = 0, ySpeed = 0;

	private BufferedImage clouds, floor, bull, wall;

	private double pivot = 1, fpivot = 0;
	private double power = 100, fpower = 0;

	private int timex = 0;

	private final double offset = 400;

	private double fps;
	private final List<Double> xPath = new ArrayList<Double>(825);// y points
																	// [x]
	private final List<Double> yPath = new ArrayList<Double>(825);// y points
																	// [x]

	private final Display display = new Display();

	private final JSlider s1 = new JSlider(0, 1000, 500);// power
	private final JSlider s2 = new JSlider(SwingConstants.VERTICAL, 0, 1000,
			500);// pivot scaled 1000x

	private boolean needFocus = true;
	private boolean isFiring = false;

	private static final int DOTSIZE = 2000;

	// private javax.swing.Timer t = new javax.swing.Timer(100,
	// new ActionListener() {
	// @Override
	// public void actionPerformed(final ActionEvent e) {
	// // Useless
	// }
	// });

	final JPanel game = new JPanel();

	private final double dotSpeed = 6, cloudSpeed = 8, floorSpeed = 10;

	Container cont = getContentPane();
	CardLayout mainCardLayout = new CardLayout();

	private final double gravity = 0.001, dt = 5;
	// private final double dt = 0.001;

	final int SIZE = 100;
	final ArrayList<Double> BackgroundDots = new ArrayList<Double>() {
		{
			for (int i = 0; i < Game.DOTSIZE; i++)
				add(random());
		}
	};
	final ArrayList<Double> seeds = new ArrayList<Double>() {
		{
			for (int i = 0; i < SIZE; i++)
				add(random());
		}
	};

	private int solution, X1, X2, X3;

	private final JTextPane jtp = new JTextPane(),
			solutionPane = new JTextPane();

	private Game() {
		// only here so this class can be super of gameprep
	}

	private Game(final BufferedImage clouds, final BufferedImage floor,
			final BufferedImage bull, final BufferedImage wall) {
		super("Game");
		WaitForImage(this, this.clouds = clouds);
		WaitForImage(this, this.floor = floor);
		WaitForImage(this, this.bull = bull);
		WaitForImage(this, this.wall = wall);
	}

	/**
	 * Calculates new x,y coordinates for all objects.
	 * 
	 * @returns true if the screen needs to be repainted
	 */
	private boolean calc() {// TODO return something
		for (int i = 0; i < SIZE; i++)
			seeds.set(i, seeds.get(i) + (random() - 0.5 + BIAS) * 0.001);
		if (!isFiring)
			return true;
		if (xPath.get(timex) > 300) {// TODO actually calculate speeds
			DotShift += dotSpeed * xSpeed;
			CloudShift += cloudSpeed * xSpeed;
			FloorShift += floorSpeed * xSpeed;
		}
		xPath.add(xPath.get(timex) + xSpeed * dt);
		yPath.add(yPath.get(timex) + ySpeed * dt);
		ySpeed -= gravity * dt;
		if (yPath.get(timex) < 303)
			if (abs(ySpeed) < 0.01) {
				// System.out.println(solutionPane.getText());
				// System.out.println("HERE");
				// System.out.println(solution);
				if (solutionPane.getText().matches("" + solution + "")) {
					solutionPane.setForeground(Color.BLUE);// right
					score += 500;
				} else {
					solutionPane.setForeground(Color.RED);// wrong
					score -= 500;
				}
				isFiring = false;// done
			} else
				ySpeed = abs(ySpeed) * 0.8;// bounce
		timex++;
		Thread.yield();
		return true;
	}

	private void fire() {
		if (isFiring)
			return;
		timex = 0;
		fpower = power;
		fpivot = pivot;
		xSpeed = cos(fpivot);
		ySpeed = sin(fpivot);
		xPath.clear();
		yPath.clear();
		xPath.add(0.0);
		yPath.add(offset);
		X1 = (int) (random() * 10);
		X2 = (int) (random() * 5);
		X3 = (int) (random() * 3);
		jtp.setText("" + X3 + "^3 - " + X2 + "^2 +" + X1 + " =");
		solution = (int) (pow(X3, 3) - pow(X2, 2) + X1);
		solutionPane.setForeground(Color.BLACK);
		solutionPane.setText("");
		isFiring = true;
	}

	private void init() {
		jtp.setEditable(false);
		jtp.setFont(jtp.getFont().deriveFont(40.0f));
		solutionPane.setFont(solutionPane.getFont().deriveFont(60.0f));
		cont.setLayout(mainCardLayout);
		cont.add(game, "game");
		cont.add(new introPanel(), "intro");

		game.setLayout(new BorderLayout());
		// game.add(BorderLayout.SOUTH, new RadioPanel());
		game.add(BorderLayout.CENTER, display);

		display.addMouseListener(this);
		display.addMouseMotionListener(this);
		display.addKeyListener(this);

		game.add(BorderLayout.NORTH, b1);
		game.add(BorderLayout.SOUTH, new JPanel() {
			{
				setLayout(new BorderLayout());
				add(s1, BorderLayout.SOUTH);
				add(new JPanel() {
					{
						setLayout(new GridLayout(1, 2));
						add(jtp);
						add(solutionPane);
					}
				}, BorderLayout.CENTER);
			}
		});// TODO
		game.add(BorderLayout.EAST, s2);// power (0->1000)
		s1.addChangeListener(new ChangeListener() {
			@Override
			public void stateChanged(final ChangeEvent e) {
				power = s1.getValue();
			}
		});
		s2.addChangeListener(new ChangeListener() {
			@Override
			public void stateChanged(final ChangeEvent e) {
				pivot = s2.getValue() / 2000.0 * PI;
			}
		});
		cont.add(BorderLayout.NORTH, new ModePanel());
		new Refresh().start();
	}

	@Override
	public void keyPressed(final KeyEvent e) {
		move(e.getKeyCode());
	}

	@Override
	public void keyReleased(final KeyEvent e) {
	}

	@Override
	public void keyTyped(final KeyEvent e) {
	}

	@Override
	public void mouseClicked(final MouseEvent e) {
	}

	@Override
	public void mouseDragged(final MouseEvent e) {
	}

	@Override
	public void mouseEntered(final MouseEvent e) {

	}

	@Override
	public void mouseExited(final MouseEvent e) {

	}

	@Override
	public void mouseMoved(final MouseEvent e) {

	}

	@Override
	public void mousePressed(final MouseEvent e) {
		needFocus = true;
	}

	@Override
	public void mouseReleased(final MouseEvent e) {
	}

	private void move(final int d) {
		switch (d) {
		case KeyEvent.VK_DOWN:
		case KeyEvent.VK_S:
			s2.setValue(s2.getValue() - 100);
			break;
		case KeyEvent.VK_UP:
		case KeyEvent.VK_W:
			s2.setValue(s2.getValue() + 100);
			break;
		case KeyEvent.VK_RIGHT:
		case KeyEvent.VK_D:
			s1.setValue(s1.getValue() - 100);
			break;
		case KeyEvent.VK_LEFT:
		case KeyEvent.VK_A:
			s1.setValue(s1.getValue() + 100);
			break;
		default:
			return;
		}
	}

	private void WaitForImage(final JFrame component, final Image image) {
		final MediaTracker tracker = new MediaTracker(component);
		try {
			tracker.addImage(image, 0);
			tracker.waitForID(0);
		} catch (final InterruptedException e) {
			e.printStackTrace();
		}
	}
}